package blockchain

import (
	"aqchain/pkg/blockchain/contract/auction"
	"aqchain/pkg/blockchain/contract/item"
	"aqchain/pkg/blockchain/contract/license"
	"aqchain/pkg/blockchain/contract/sale"
	"aqchain/pkg/config"
	"aqchain/pkg/log"
	"context"
	"gitee.com/aqchain/go-ethereum/common"
	"gitee.com/aqchain/go-ethereum/ethclient"
	"gitee.com/aqchain/go-ethereum/rpc"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// Client 用于连接区块链和获取区块链数据
type Client struct {
	config          config.Config
	db              *gorm.DB
	client          *ethclient.Client // 以太坊RPC连接
	itemContract    *item.ItemContract
	saleContract    *sale.SaleContract
	auctionContract *auction.AuctionContract
	licenseContract *license.LicenseContract

	Err chan error
}

func New(cfg config.Config, db *gorm.DB) Client {
	log.Logger.Debug("连接区块链节点", zap.String("RPCAddress", cfg.Ethereum.Client.RPCAddress))
	// 连接区块链节点
	rpcClient, err := rpc.Dial(cfg.Ethereum.Client.RPCAddress)
	if err != nil {
		log.Logger.Panic("连接错误", zap.String("error", err.Error()))
	}
	client := ethclient.NewClient(rpcClient)
	fileContractAddress := common.HexToAddress(cfg.Ethereum.Contracts.Item)
	log.Logger.Debug("连接成功")
	log.Logger.Debug("确权合约", zap.String("ContractAddress", fileContractAddress.String()))
	itemContract, err := item.NewItemContract(fileContractAddress, client)
	if err != nil {
		log.Logger.Panic("确权合约错误", zap.String("error", err.Error()))
	}
	saleContractAddress := common.HexToAddress(cfg.Ethereum.Contracts.Sale)
	log.Logger.Debug("定价交易合约", zap.String("ContractAddress", saleContractAddress.String()))
	saleContract, err := sale.NewSaleContract(saleContractAddress, client)
	if err != nil {
		log.Logger.Panic("定价交易合约错误", zap.String("error", err.Error()))
	}
	aucitonContractAddress := common.HexToAddress(cfg.Ethereum.Contracts.Auction)
	log.Logger.Debug("拍卖交易合约", zap.String("ContractAddress", aucitonContractAddress.String()))
	auctionContract, err := auction.NewAuctionContract(aucitonContractAddress, client)
	if err != nil {
		log.Logger.Panic("定价交易合约错误", zap.String("error", err.Error()))
	}
	licenseContractAddress := common.HexToAddress(cfg.Ethereum.Contracts.License)
	log.Logger.Debug("拍卖交易合约", zap.String("ContractAddress", licenseContractAddress.String()))
	licenseContract, err := license.NewLicenseContract(licenseContractAddress, client)
	if err != nil {
		log.Logger.Panic("定价交易合约错误", zap.String("error", err.Error()))
	}
	return Client{
		config:          cfg,
		client:          client,
		itemContract:    itemContract,
		saleContract:    saleContract,
		auctionContract: auctionContract,
		licenseContract: licenseContract,
		db:              db,
		Err:             make(chan error),
	}
}

func (c *Client) Run() error {
	log.Logger.Info("开始监听链上事件")
	// 每个合约一个协程 这种写法一旦有一个协程报错会停止整个程序 类似SubscribeItemContractEvent中的错误处理有更简单的写法为了统一格式写了现在的方式
	ctx, cancel := context.WithCancel(context.TODO())
	go func() {
		err := c.SubscribeItemContractEvent(ctx)
		if err != nil {
			c.Err <- err
		}
	}()

	go func() {
		err := c.SubscribeSaleContractEvent(ctx)
		if err != nil {
			c.Err <- err
		}
	}()

	go func() {
		err := c.SubscribeAuctionContractEvent(ctx)
		if err != nil {
			c.Err <- err
		}
	}()
	go func() {
		err := c.SubscribeLicenseContractEvent(ctx)
		if err != nil {
			c.Err <- err
		}
	}()
	select {
	case err := <-c.Err:
		cancel()
		return err
	}
}
